﻿namespace Example.Text
{
    using System;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>
    /// Base 36 is a positional numeral system using 36 as the radix.
    /// </summary>
    /// <remarks>
    /// <see href="http://wikipedia.org/wiki/Base_36">Base 36 (Wikipedia)</see>
    /// </remarks>
    public struct Base36 : IComparable
    {
        private const string _chars = "0123456789abcdefghijklmnopqrstuvwxyz";

        private static readonly Base36 _max = new Base36(int.MaxValue);

        private static readonly Base36 _min = new Base36(int.MinValue);

        private static readonly int[] _powers =
            {
                1, 36, 1296, 46656, 1679616, 60466176
            };

        private static readonly Base36 _zero = new Base36(0);

        private readonly int _value;

        public Base36(string value)
            : this(Convert(value))
        {
        }

        public Base36(int value)
        {
            _value = value;
        }

        public static Base36 MaxValue
        {
            get
            {
                return _max;
            }
        }

        public static Base36 MinValue
        {
            get
            {
                return _min;
            }
        }

        public static Base36 Zero
        {
            get
            {
                return _zero;
            }
        }

        public static Base36 operator +(Base36 operand1, 
                                        Base36 operand2)
        {
            return operand1.ToInt32() + operand2.ToInt32();
        }

        public static Base36 operator --(Base36 operand)
        {
            return operand.ToInt32() - 1;
        }

        public static Base36 operator /(Base36 operand1, 
                                        Base36 operand2)
        {
            return operand1.ToInt32() / operand2.ToInt32();
        }

        public static bool operator ==(Base36 operand1, 
                                       Base36 operand2)
        {
            return Compare(operand1, operand2) == 0;
        }

        public static bool operator >(Base36 operand1, 
                                      Base36 operand2)
        {
            return Compare(operand1, operand2) > 0;
        }

        public static implicit operator Base36(string obj)
        {
            return new Base36(obj);
        }

        public static implicit operator string(Base36 obj)
        {
            return obj.ToString();
        }

        public static implicit operator Base36(int value)
        {
            return new Base36(value);
        }

        public static implicit operator int(Base36 value)
        {
            return value.ToInt32();
        }

        public static Base36 operator ++(Base36 operand)
        {
            return operand.ToInt32() + 1;
        }

        public static bool operator !=(Base36 operand1, 
                                       Base36 operand2)
        {
            return Compare(operand1, operand2) != 0;
        }

        public static bool operator <(Base36 operand1, 
                                      Base36 operand2)
        {
            return Compare(operand1, operand2) < 0;
        }

        public static Base36 operator %(Base36 operand1, 
                                        Base36 operand2)
        {
            return operand1.ToInt32() % operand2.ToInt32();
        }

        public static Base36 operator *(Base36 operand1, 
                                        Base36 operand2)
        {
            return operand1.ToInt32() * operand2.ToInt32();
        }

        public static Base36 operator -(Base36 operand1, 
                                        Base36 operand2)
        {
            return operand1.ToInt32() - operand2.ToInt32();
        }

        public static int Compare(Base36 comparand1, 
                                  Base36 comparand2)
        {
            var compare = 0;

            if (comparand1.ToInt32() !=
                comparand2.ToInt32())
            {
                compare = (comparand1.ToInt32() > comparand2.ToInt32()) ? 1 : -1;
            }

            return compare;
        }

        public Base36 Add(Base36 value)
        {
            return this + value;
        }

        public Base36 Decrement()
        {
            return --this;
        }

        public Base36 Divide(Base36 divisor)
        {
            return this / divisor;
        }

        public override bool Equals(object obj)
        {
            var equals = false;

            if (obj != null)
            {
                if (obj is Base36)
                {
                    equals = 0 == CompareTo(obj);
                }
            }

            return equals;
        }

        public override int GetHashCode()
        {
            return _value;
        }

        public Base36 Increment()
        {
            return ++this;
        }

        public Base36 Mod(Base36 divisor)
        {
            return this % divisor;
        }

        public Base36 Multiply(Base36 multiplier)
        {
            return this * multiplier;
        }

        public Base36 Subtract(Base36 value)
        {
            return this - value;
        }

        public int ToInt32()
        {
            return _value;
        }

        public override string ToString()
        {
            string value = null;
            var remainder = Math.Abs(_value);

            while (remainder > 35)
            {
                var index = remainder % 36;
                value = _chars[index] + value;
                remainder /= 36;
            }

            //// convert the remainder
            return string.Concat((_value < 0) ? "-" : string.Empty, _chars[remainder], value);
        }

        public int CompareTo(object obj)
        {
            int compare;

            if (obj == null)
            {
                compare = 1;
            }
            else
            {
                if (obj is Base36)
                {
                    compare = Compare(this, (Base36)obj);
                }
                else
                {
                    throw new ArgumentOutOfRangeException("obj");
                }
            }

            return compare;
        }

        [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "The characters are supposed to be lower case.")]
        private static int Convert(string expression)
        {
            if (expression == null)
            {
                throw new ArgumentNullException("expression");
            }
            else if (expression.Length == 0)
            {
                throw new ArgumentOutOfRangeException("expression");
            }

            var value = 0;
            var radix = 0;

            var negative = false;
            if (expression.StartsWith("-", StringComparison.OrdinalIgnoreCase))
            {
                negative = true;
                expression = expression.Substring(1);
            }

            expression = expression.ToLowerInvariant();
            for (var i = expression.Length - 1; i > -1; i--)
            {
                if (!_chars.Contains(expression[i].ToString()))
                {
                    throw new FormatException("A base-64 string can only contain characters in the range [0-9] and [Aa-Zz].");
                }

                value += _chars.IndexOf(expression[i]) * _powers[radix++];
            }

            return negative ? (0 - value) : value;
        }
    }
}